#include <linux/cpu.h>
#include <linux/smp.h>
#include <linux/hlist.h>
#include <linux/errno.h>
#include <linux/bug.h>
#include <linux/sched.h>
#include <trace/events/cpuhp.h>
#include <linux/minmax.h>
#include <linux/sched/hotplug.h>
#include <linux/kthread.h>

#include "inc/smpboot.h"
#include "inc/cpu_state.h"
#include "inc/stop_machine.h"

struct cpuhp_step
{
    const char *name;
    union
    {
        int (*single)(unsigned int cpu);
        int (*multi)(unsigned int cpu, struct hlist_node *node);
    } startup;
    union
    {
        int (*single)(unsigned int cpu);
        int (*multi)(unsigned int cpu,
                     struct hlist_node *node);
    } teardown;
    /* private: */
    struct hlist_head list;
    /* public: */
    bool cant_stop;
    bool multi_instance;
};

/* Synchronization state management */
enum cpuhp_sync_state
{
    SYNC_STATE_DEAD,
    SYNC_STATE_KICKED,
    SYNC_STATE_SHOULD_DIE,
    SYNC_STATE_ALIVE,
    SYNC_STATE_SHOULD_ONLINE,
    SYNC_STATE_ONLINE,
};

static int bringup_cpu(unsigned int cpu);
static int finish_cpu(unsigned int cpu);

/* Boot processor state steps */
static struct cpuhp_step cpuhp_state_steps[] = {

    [CPUHP_BRINGUP_CPU] = {
        .name = "cpu:bringup",
        .startup.single = bringup_cpu,
        .teardown.single = finish_cpu,
        .cant_stop = true,
    },

#ifdef CONFIG_SMP
    [CPUHP_CREATE_THREADS] = {
        .name = "threads:prepare",
        .startup.single = smpboot_create_threads,
        .teardown.single = NULL,
        .cant_stop = true,
    },

    /* Handle smpboot threads park/unpark */
    [CPUHP_AP_SMPBOOT_THREADS] = {
        .name = "smpboot/threads:online",
        .startup.single = smpboot_unpark_threads,
        .teardown.single = smpboot_park_threads,
    },

    /* Last state is scheduler control setting the cpu active */
    [CPUHP_AP_ACTIVE] = {
        .name = "sched:active",
        .startup.single = sched_cpu_activate,
        .teardown.single = sched_cpu_deactivate,
    },
#endif

    [CPUHP_ONLINE] = {},
};

#include "_cpuhp_/smt.c"
#include "_cpuhp_/core_sync.c"
#include "_cpuhp_/state.c"
#include "_cpuhp_/invoke_callback.c"
#include "_cpuhp_/setup.c"
#include "_cpuhp_/bringup.c"
#include "_cpuhp_/hotplug_thread.c"

static inline void cpuhp_ap_update_sync_state(enum cpuhp_sync_state state) {}

/*
 * Called from the idle task. Wake up the controlling task which brings the
 * hotplug thread of the upcoming CPU up and then delegates the rest of the
 * online bringup to the hotplug thread.
 */
void cpuhp_online_idle(enum cpuhp_state state)
{
    struct cpuhp_cpu_state *st = this_cpu_ptr(&cpuhp_state);

    /* Happens for the boot cpu */
    if (state != CPUHP_AP_ONLINE_IDLE)
        return;

    cpuhp_ap_update_sync_state(SYNC_STATE_ONLINE);

    /*
     * Unpark the stopper thread before we start the idle loop (and start
     * scheduling); this ensures the stopper task is always available.
     */
    stop_machine_unpark(smp_processor_id());

    st->state = CPUHP_AP_ONLINE_IDLE;
    complete_ap_thread(st, true);
}

__init void bringup_nonboot_cpus(unsigned int max_cpus)
{
    if (!max_cpus)
        return;

    /* Full per CPU serialized bringup */
    cpuhp_bringup_mask(cpu_present_mask(), max_cpus, CPUHP_ONLINE);
}

void cpuhp_invoke_callback_range_nofail(bool bringup,
                                        unsigned int cpu,
                                        struct cpuhp_cpu_state *st,
                                        enum cpuhp_state target)
{
    __cpuhp_invoke_callback_range(bringup, cpu, st, target, true);
}

int __cpuhp_setup_state_cpuslocked(enum cpuhp_state state,
                                   const char *name, bool invoke,
                                   int (*startup)(unsigned int cpu),
                                   int (*teardown)(unsigned int cpu),
                                   bool multi_instance)
{
    int cpu, ret = 0;
    bool dynstate;

    lockdep_assert_cpus_held();

    if (cpuhp_cb_check(state) || !name)
        return -EINVAL;

    // mutex_lock(&cpuhp_state_mutex);

    ret = cpuhp_store_callbacks(state, name, startup, teardown,
                                multi_instance);

    dynstate = state == CPUHP_AP_ONLINE_DYN || state == CPUHP_BP_PREPARE_DYN;
    if (ret > 0 && dynstate)
    {
        state = ret;
        ret = 0;
    }

    if (ret || !invoke || !startup)
        goto out;

    /*
     * Try to call the startup callback for each present cpu
     * depending on the hotplug state of the cpu.
     */
    for_each_present_cpu(cpu)
    {
        struct cpuhp_cpu_state *st = per_cpuhp_cpu_state(cpu);
        int cpustate = st->state;

        if (cpustate < state)
            continue;

        ret = cpuhp_issue_call(cpu, state, true, NULL);
        if (ret)
        {
            if (teardown)
                cpuhp_rollback_install(cpu, state, NULL);
            cpuhp_store_callbacks(state, NULL, NULL, NULL, false);
            goto out;
        }
    }
out:
    // todo mutex_unlock(&cpuhp_state_mutex);
    /*
     * If the requested state is CPUHP_AP_ONLINE_DYN or CPUHP_BP_PREPARE_DYN,
     * return the dynamically allocated state in case of success.
     */
    if (!ret && dynstate)
        return state;
    return ret;
}

int __cpuhp_setup_state(enum cpuhp_state state,
                        const char *name, bool invoke,
                        int (*startup)(unsigned int cpu),
                        int (*teardown)(unsigned int cpu),
                        bool multi_instance)
{
    int ret;

    ret = __cpuhp_setup_state_cpuslocked(state, name, invoke, startup,
                                         teardown, multi_instance);

    return ret;
}

int cpuhp_setup_state(enum cpuhp_state state,
                      const char *name,
                      int (*startup)(unsigned int cpu),
                      int (*teardown)(unsigned int cpu))
{
    return __cpuhp_setup_state(state, name, true, startup, teardown, false);
}

void __init cpuhp_threads_init(void)
{
    struct cpuhp_cpu_state *st = this_cpu_ptr(&cpuhp_state);

    cpuhp_init_state();
    BUG_ON(smpboot_register_percpu_thread(&cpuhp_threads));
    kthread_unpark(st->thread);
}

/*
 * Must be called _AFTER_ setting up the per_cpu areas
 */
void __init boot_cpu_hotplug_init(void)
{
    cpuhp_state.state = CPUHP_ONLINE;
    cpuhp_state.target = CPUHP_ONLINE;
}
